using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;

using System.IO;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Threading;
using System.Collections.Generic;

namespace MultiLevelSelectField
{
    public class MultiLevelSelectField : SPFieldText
    {
        private const string THREADDATANAME_LISTURL = "ListUrl";

        #region Private fields
        private string _listurl;
        private bool _preventUpdate = false;
        #endregion

        #region Constructors
        public MultiLevelSelectField(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName)
        {
        }

        public MultiLevelSelectField(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName)
        {
        }
        #endregion

        #region Methods used for temporary storage
        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/cc889345.aspx
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="value"></param>
        protected void SetNamedThreadData(string propertyName, object value)
        {
            Thread.SetData(Thread.GetNamedDataSlot(propertyName), value);
        }

        protected void ClearNamedThreadData(string propertyName)
        {
            Thread.FreeNamedDataSlot(propertyName);
        }

        protected object GetNamedThreadData(string propertyName)
        {
            return Thread.GetData(Thread.GetNamedDataSlot(propertyName));
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Returns an specialized FieldControl for this Field/FieldType
        /// </summary>
        public override Microsoft.SharePoint.WebControls.BaseFieldControl FieldRenderingControl
        {
            get
            {
                BaseFieldControl control = new MultiLevelSelectFieldControl();
                control.FieldName = this.InternalName;
                return control;
            }
        }

        /// <summary>
        /// The Url to the childlist
        /// </summary>
        public string ListUrl
        {
            get
            {
                if (_listurl == null)
                {
                    SPFieldUrlValue value = (SPFieldUrlValue)GetCustomProperty("ListUrl");
                    _listurl = (value != null) ? value.Url : null;
                }

                return _listurl;
            }
            set
            {
                SPFieldUrlValue fv = null;
                if (!string.IsNullOrEmpty(value)) fv = new SPFieldUrlValue(value);
                _listurl = value;
                SetNamedThreadData(THREADDATANAME_LISTURL, fv);
            }
        }
        #endregion

        #region Public methods
        /// <summary>
        /// override the OnAdded method to call Update
        /// because Update is not called when creating new fields 
        /// </summary>
        public override void OnAdded(SPAddFieldOptions op)
        {
            _preventUpdate = true;
            SPFieldUrlValue listurl = (SPFieldUrlValue)GetNamedThreadData(THREADDATANAME_LISTURL);
            SetCustomProperty("ListUrl", listurl);
            Update();
            ClearNamedThreadData(THREADDATANAME_LISTURL);
        }

        public override void OnUpdated()
        {
            if (_preventUpdate == false)
            {
                _preventUpdate = true;
                SPFieldUrlValue listurl = (SPFieldUrlValue)GetNamedThreadData(THREADDATANAME_LISTURL);
                SetCustomProperty("ListUrl", listurl);
                Update();
                ClearNamedThreadData(THREADDATANAME_LISTURL);
            }
        }

        public override object GetFieldValue(string value)
        {
            MultiLevelSelectFieldValue v = new MultiLevelSelectFieldValue(value);
            return v;
        }

        public SPListItem FindValue(string fieldname, string value)
        {
            return FindValue(SPContext.Current.Site, fieldname, value);
        }

        private SPFieldLookup GetParentLookupField(SPSite site, SPList currentlist)
        {
            SPFieldLookup sfl = null;

            // Find lookup-field in this list
            foreach (SPField field in currentlist.Fields)
            {
                if (field is SPFieldLookup && !field.FromBaseType)
                {
                    if (sfl != null) throw new Exception("This list has multiple lookupfields which is not supported!");
                    sfl = (SPFieldLookup)field;
                }
            }
            return sfl;
        }

        /// <summary>
        /// This method returns the list which is one hiearchy higher: parent
        /// </summary>
        /// <param name="site"></param>
        /// <param name="currentlist"></param>
        /// <returns>Null or the parentlist</returns>
        public SPList GetParentList(SPSite site, SPList currentlist)
        {
            SPFieldLookup sfl = GetParentLookupField(site, currentlist);

            // Is a lookup-field found?
            if (sfl != null)
            {
                using (SPWeb parentlistweb = site.OpenWeb(sfl.LookupWebId))
                {

                    Guid parentlistguid = Guid.Empty;
                    try
                    {
                        parentlistguid = new Guid(sfl.LookupList);
                    }
                    catch
                    {
                        parentlistguid = Guid.Empty;
                    }

                    SPList parentlist = null;
                    if (!parentlistguid.Equals(Guid.Empty))
                    {
                        parentlist = parentlistweb.Lists[parentlistguid];
                    }
                    else
                    {
                        parentlist = parentlistweb.Lists[sfl.LookupList];
                    }

                    return parentlist;
                }
            }

            return null;
        }

        /// <summary>
        /// Finds the listitem in the chain of lists which Title-field matches the given value.
        /// </summary>
        /// <param name="value">Value of title</param>
        /// <returns></returns>
        public SPListItem FindTitleValue(string value)
        {
            if (SPContext.Current == null) throw new Exception("No SPContext");
            return FindValue(SPContext.Current.Site, "Title", value);
        }


        /// <summary>
        /// Finds the listitem in the chain of lists which Title-field matches the given value.
        /// </summary>
        /// <param name="site"></param>
        /// <param name="value">Value of title</param>
        /// <returns></returns>
        public SPListItem FindTitleValue(SPSite site, string value)
        {
            return FindValue(site, "Title", value);
        }


        /// <summary>
        /// This method finds a value within the lists
        /// </summary>
        /// <param name="value">The value</param>
        /// <returns>The listitem</returns>
        public SPListItem FindValue(SPSite site, string fieldname, string value)
        {
            // Start with the given list
            SPList currentlist = null;

            using (SPWeb web = site.OpenWeb(ListUrl))
            {
                currentlist = web.GetList(ListUrl);


                // Continue until the currentlist is null
                while (currentlist != null)
                {

                    SPListItemCollection listitems = null;
                    if (currentlist.Fields.ContainsField(fieldname))
                    {
                        SPField field = currentlist.Fields[fieldname];
                        SPQuery titlequery = new SPQuery();
                        titlequery.Query = string.Format("<Where><Eq><FieldRef Name=\"{0}\" /><Value Type=\"{2}\">{1}</Value></Eq></Where>", fieldname, value, field.TypeAsString);
                        listitems = currentlist.GetItems(titlequery);
                    }

                    // Did we find the value
                    if (listitems != null && listitems.Count > 0)
                    {
                        if (listitems.Count > 1) throw new Exception(string.Format("Multiple items matched the Value '{0}' in column '{1}' of list '{2}'.", value, fieldname, currentlist.RootFolder.ServerRelativeUrl));
                        return listitems[0];
                    }

                    SPList parentlist = GetParentList(site, currentlist);
                    currentlist = parentlist;
                }
            }
            return null;
        }

        public SPListItem GetParentListItem(SPSite site, SPListItem listitem)
        {
            SPList parentlist = GetParentList(site, listitem.ParentList);
            if (parentlist != null)
            {
                SPFieldLookup sfl = GetParentLookupField(site, listitem.ParentList);
                if (sfl != null)
                {
                    SPFieldLookupValue lv = new SPFieldLookupValue((string)listitem[sfl.Id]);
                    return parentlist.GetItemById(lv.LookupId);
                }
            }
            return null;
        }

        public SPListItemCollection GetChildListitems(SPListItem item)
        {
            if (SPContext.Current == null) throw new Exception("No context");
            return GetChildListitems(SPContext.Current.Site, item);
        }

        /// <summary>
        /// Returns a collection of the first-level-children of the given listitem
        /// </summary>
        /// <param name="site"></param>
        /// <param name="item"></param>
        /// <returns>A collection of only the first-level-children</returns>
        public SPListItemCollection GetChildListitems(SPSite site, SPListItem item)
        {
            List<SPListItem> listitems = new List<SPListItem>();

            // Start with the given list
            SPList currentlist = null;

            using (SPWeb web = site.OpenWeb(ListUrl))
            {
                currentlist = web.GetList(ListUrl);


                while (currentlist != null)
                {
                    SPList parentlist = GetParentList(site, currentlist);
                    if (parentlist != null && parentlist.ID.Equals(item.ParentList.ID))
                    {
                        SPFieldLookup sfl = GetParentLookupField(site, currentlist);
                        SPQuery query = new SPQuery();
                        query.Query = string.Format("<Where><Eq><FieldRef Name=\"{0}\" LookupId=\"TRUE\"/><Value Type=\"Lookup\">{1}</Value></Eq></Where>", sfl.InternalName, item.ID);
                        return currentlist.GetItems(query);
                    }

                    currentlist = null;
                    if (parentlist != null)
                    {
                        currentlist = parentlist;
                    }
                }
            }

            throw new Exception("The given item is not in the chain of linked-lists!");
        }

        public string[] GetPathsDown(SPListItem item, string seperator)
        {
            try
            {
                List<SPFieldLookup> levels = new List<SPFieldLookup>();

                SPList list = null;


                using (SPWeb web = item.ParentList.ParentWeb.Site.OpenWeb(this.ListUrl))
                {
                    list = web.GetList(this.ListUrl);

                    while (list != null && list.ID != item.ParentList.ID)
                    {
                        SPFieldLookup sfl = null;
                        foreach (SPField field in list.Fields)
                        {
                            if (field is SPFieldLookup && !field.FromBaseType)
                            {
                                if (sfl != null) throw new Exception("This list has multiple lookupfields which is not supported!");
                                sfl = (SPFieldLookup)field;
                            }
                        }

                        if (sfl != null)
                        {
                            using (SPWeb web2 = item.ParentList.ParentWeb.Site.OpenWeb(sfl.LookupWebId))
                            {
                                if (sfl.LookupList.Contains("{"))
                                {
                                    list = web2.Lists[new Guid(sfl.LookupList)];
                                }
                                else
                                {
                                    list = web2.Lists[sfl.LookupList];
                                }
                            }
                            levels.Add(sfl);
                        }
                        else list = null;
                    }
                    string prefixing = item.Title;

                    SPListItem item2 = item;
                    list = item2.ParentList;
                    while (list != null)
                    {
                        SPFieldLookup sfl = null;
                        foreach (SPField field in item2.ParentList.Fields)
                        {
                            if (field is SPFieldLookup && !field.FromBaseType)
                            {
                                if (sfl != null) throw new Exception("This list has multiple lookupfields which is not supported!");
                                sfl = (SPFieldLookup)field;
                            }
                        }

                        if (sfl != null)
                        {
                            using (SPWeb web2 = item2.ParentList.ParentWeb.Site.OpenWeb(sfl.LookupWebId))
                            {
                                if (sfl.LookupList.Contains("{"))
                                {
                                    list = web2.Lists[new Guid(sfl.LookupList)];
                                }
                                else
                                {
                                    list = web2.Lists[sfl.LookupList];
                                }
                            }

                            SPFieldLookupValue sflv = new SPFieldLookupValue((string)item2[sfl.Id]);
                            item2 = list.GetItemById(sflv.LookupId);
                            prefixing = item2.Title + seperator + prefixing;
                        }
                        else list = null;
                    }

                    List<string> paths = new List<string>();
                    if (levels.Count > 0)
                    {
                        Follow(paths, levels, prefixing, item, seperator);
                    }
                    paths.Add(prefixing);
                    return paths.ToArray();
                }
            }
            catch (Exception ex)
            {
                SPSecurity.RunWithElevatedPrivileges(delegate
                {
                    EventLog.WriteEntry(this.GetType().FullName, ex.ToString(), EventLogEntryType.Error);
                }
                );
            }
            return null;
        }

        private void Follow(List<string> paths, List<SPFieldLookup> levels, string curpath, SPListItem item, string Seperator)
        {
            if (levels.Count > 0)
            {
                SPFieldLookup sfl = levels[0];

                SPQuery query = new SPQuery();
                query.Query = string.Format("<Where><Eq><FieldRef Name=\"{0}\" LookupId=\"TRUE\"/><Value Type=\"{1}\">{2}</Value></Eq></Where>", sfl.InternalName, item.Fields[item.Fields.GetFieldByInternalName(sfl.LookupField).Title].TypeAsString, item.ID);
                SPListItemCollection childs = sfl.ParentList.GetItems(query);
                if (childs != null)
                {
                    List<SPFieldLookup> clevels = new List<SPFieldLookup>();
                    for (int idx = 1; idx < levels.Count; idx++) clevels.Add(levels[idx]);
                    foreach (SPListItem child in childs)
                    {
                        Follow(paths, clevels, curpath + Seperator + child.Title, child, Seperator);
                    }
                }
            }
            else
            {
                paths.Add(curpath);
            }
        }

        public string[] GetPathsUp(SPListItem item, string seperator)
        {
            try
            {
                Stack<string> levels = new Stack<string>();

                while (item != null)
                {
                    levels.Push(item.Title);

                    SPFieldLookup sfl = null;

                    // Find lookup-field in this list
                    foreach (SPField field in item.ParentList.Fields)
                    {
                        if (field is SPFieldLookup && !field.FromBaseType)
                        {
                            if (sfl != null) throw new Exception("This list has multiple lookupfields which is not supported!");
                            sfl = (SPFieldLookup)field;
                        }
                    }

                    if (sfl != null)
                    {
                        SPFieldLookupValue sflv = new SPFieldLookupValue((string)item[sfl.Id]);
                        using (SPWeb web = item.ParentList.ParentWeb.Site.OpenWeb(sfl.LookupWebId))
                        {
                            SPList list = null;
                            if (sfl.LookupList.Contains("{"))
                            {
                                list = web.Lists[new Guid(sfl.LookupList)];
                            }
                            else
                            {
                                list = web.Lists[sfl.LookupList];
                            }
                            item = list.GetItemById(sflv.LookupId);
                        }
                    }
                    else item = null;
                }


                List<string> paths = new List<string>();
                string level = "";
                while (levels.Count > 0)
                {
                    if (level.Length > 0) level += seperator;
                    level += levels.Pop();
                    paths.Add(level);
                }
                return paths.ToArray();
            }
            catch (Exception ex)
            {
                SPSecurity.RunWithElevatedPrivileges(
                    delegate
                    {
                        EventLog.WriteEntry(this.GetType().FullName, ex.ToString(), EventLogEntryType.Error);
                    });
            }
            return null;
        }

        #endregion
    }
}
